package org.mtmi.ui.controls;

import org.mtmi.tuio.listeners.DragListener;
import org.mtmi.tuio.listeners.TUIODragEvent;
import org.mtmi.tuio.listeners.TUIOTapEvent;
import org.mtmi.ui.GraphicUtil;
import org.mtmi.ui.scenes.Scene;

/**
 * provide common area for painted and skin Knob.
 *
 */
public abstract class AbstractKnob extends AbstractRangeValueControl implements DragListener 
{
	
	private boolean isTapped;
	private int amin, amove; //angle (degree) for vmin, and oriented angle of knob move.
	
	public AbstractKnob( Scene scene, int flags ) {
		super( scene, flags );
		isTapped = false;
		amin = -135;
		amove = -270;
	}
	
	/** create knob with range and gle value for angles -135,+270 degree */
	public AbstractKnob(Scene scene, int valCurrent, int valMin, int valMax, int _flags ) {
		super( scene, valCurrent, valMin, valMax, _flags );
		isTapped = false;
		amin = -135;
		amove = -270;
	}
	
	/** @return the angle corresponding to minimum value (degree, 0 is at 3 o'clock, +90 at midday) */
	public int getAngleVMin() { return amin; }
	/** change angle corresponding to mininum value */
	public void setAngleVMin( int am ) { amin=am; }
	
	/** @return the angle length corresponding knob movement (negative for counter clockwise) */
	public int getDAngle() { return amove; }
	/** change angle length corresponding to knob movement (negative for counter clockwise) */
	public void setDAngle( int al ) {
		if( amove==0 || amove >360 ) throw new IllegalArgumentException(" angle length not in ]0..360]"); //$NON-NL1-1$
		amove=al; 
	}
	
	/** @return the absolute scene x abscissa of knob center, by default at center of bounds */
	public int getCX() { return location.x+size.x/2; }
	/** @return the absolute scene y abscissa of knob center, by default at center of bounds */
	public int getCY() { return location.y+size.y/2; }
	
	/** return current angle of knob, relative from getAngleVMin() */
	protected int getAngle() {
		int value = getValue();
		int da = Math.round( getDAngle()*(value-valMin)/(valMax-valMin) );
		return da;
	}
	
	@Override
	/** check if the point is inside knob bounds */
	public boolean containsTouch(TouchControlEvent e) {
		return GraphicUtil.rectContains( location, size, e.x, e.y );
	}

	@Override
	public String toString() {
		return super.toString()+"{value:"+getValue()+"}";
	}

	/** @return true if user currently hold the knob to move it */
	protected boolean isMoving() { return isTapped; }
	
	@Override
	public void tapDown(TUIOTapEvent event) {
		isTapped = true;
	}

	@Override
	public void tapUp(TUIOTapEvent event) {
		isTapped = false;
		redraw();
	}
	
	@Override
	public void drag( TUIODragEvent e )
	{
		int cx = getCX();
		int cy = getCY();
		
		//note: y are reversed
		double a = Math.atan2( -(e.ey-cy), e.ex-cx );
		int ia = (int)Math.round( 180*a/Math.PI );
		
		int da = getDAngle();
		int amin = getAngleVMin();
		int amax = getAngleVMin()+da;
		int nv;
		
		if( da > 0 ) {
			while( ia < amin ) ia+= 360;
			int amid = amax + (360-da)/2;
			if( ia > amid ) nv = valMin;
			else if( ia>=amax ) nv = valMax;
			else {
				float k = (ia-amin)/(float)da; //0,,1
				nv = Math.round( k*(valMax-valMin)+valMin );
			}
		}
		else { // da<0: means amax is < amin
			while( ia > amin ) ia-=360;
			int amid = amax-(360+da)/2;
			if( ia < amid ) nv = valMin;
			else if( ia < amax ) nv = valMax;
			else {
				float k = (ia-amin)/(float)da; //0,,1
				nv = Math.round( k*(valMax-valMin)+valMin );
			}
		}
		
		if( nv != getValue() ) {
			setValue( nv );
			redraw();
			notifyValueChanged();	
		}
	}


}
